#include "DlmComm.h"
#include "LoggerModule/Logger.h"
#include <unistd.h>
#include <cassert>
#include <sys/param.h>
#include <iostream>
#include <cstring>


DlmComm::DlmComm(int dlm_fd) :	dlmFd_(dlm_fd)
{
	pthread_mutex_init(&mutex_, NULL);
}

DlmComm::~DlmComm()
{
	pthread_mutex_destroy(&mutex_);
}

bool DlmComm::clientRegister(pid_t client_pid, int client_fd)
{
	pthread_mutex_lock(&mutex_);
	bool inserted = clients_.insert(std::make_pair(client_pid, client_fd)).second;
	pthread_mutex_unlock(&mutex_);
	
	return inserted;
}

void DlmComm::clientUnregister(pid_t client_pid)
{		
	pthread_mutex_lock(&mutex_);
	if(clients_.erase(client_pid) == 1)
		Logger::getInstance().logDlm(Logger::INFO, "[DLM COMM] Process unregistered: %d.", client_pid);
	pthread_mutex_unlock(&mutex_);		
}

bool DlmComm::isClientRegistered(pid_t client_pid) const
{
	bool found;
	
	pthread_mutex_lock(&mutex_);	
	found = (clients_.find(client_pid) != clients_.end());
	pthread_mutex_unlock(&mutex_);	
	
	return found;
}

bool DlmComm::send(pid_t client_pid, const void *data, unsigned int data_size)
{

	int fd;
	
	// the write to the pipe should be atomic
	assert(data_size <= PIPE_BUF);
	
	pthread_mutex_lock(&mutex_);
	{	
		Clients::const_iterator fd_it = clients_.find(client_pid);
		if(fd_it == clients_.end())
		{
			pthread_mutex_unlock(&mutex_);
			Logger::getInstance().logDlm(Logger::ERROR, "[DLM COMM] Client with pid %d not registered", client_pid);				
			return false;
		}
		fd = fd_it->second;
	}	
	pthread_mutex_unlock(&mutex_);		
		
	int sent = write(fd, data, data_size);	
	if(sent < 0)
	{
		Logger::getInstance().logDlm(Logger::ERRNO, "[DLM COMM] Error while sending %dB of data to client pid %d", data_size, client_pid);						
		return false;
	}
	
	// atomic write so if write didn't failed it should send everything
	assert((unsigned int)sent == data_size);
	
	return true;
}
		
bool DlmComm::receive(char *buffer, unsigned int buffer_len)
{
	int rcvd = read(dlmFd_, buffer, buffer_len);
	if(rcvd < 0)
	{
		Logger::getInstance().logDlm(Logger::ERRNO, "[DLM COMM] Error while reading data");		
		return false;
	}
	// we don't wont to read more than PIPE_BUF bytes
		
	return true;
}

bool DlmComm::sendResponse(int client_pid, ResponseCode code)
{
	char ccode = code;
	return send(client_pid, &ccode, 1);
}

Request* DlmComm::receiveRequest()
{
	char code;
	if(!receive(&code, 1))
		return NULL;
	
	Request *request = NULL;
	
	switch(code)
	{
		case DLM_LOCK:
			request = createRequest<LockRequest>(code);
			break;
		case DLM_UNLOCK:
			request = createRequest<UnlockRequest>(code);
			break;
		case DLM_TRYLOCK:
			request = createRequest<TryLockRequest>(code);
			break;
		default:
			Logger::getInstance().logDlm(Logger::ERROR, "[DLM COMM] Received invalid request code");						
	}
	
	return request;
}

template<typename R> 
Request* DlmComm::createRequest(char code)
{
	char data[PIPE_BUF];
	data[0] = code;
	if(!receive(&data[1], sizeof(R)-1))
		return NULL;

	return new R(*(R*)data); // must be deleted with delete operator	
}
